using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using Engine.Business.Abstract;
using HunterPluginSDK;
using HunterPluginSDK.Interface;


namespace Engine.CSV
{
    //public delegate void ItemFound (string entity, Business.Abstract.CollectionItem ci, ItemState state, object param);
    public enum ItemState{Start, New, Duplicated, Error, Valid, Missing, End, Total, ReportProgress};
    
    public class CsvHunter:
        Business.Interfaces.IHunter,
        Business.Interfaces.IMatchFile
    {
        #region MEMBERS
        CsvHuntInfo HuntInfo   = null;
        Business.Interfaces.IScanner Scanner = null;
        private IO.FileTools IOTools        = null;
        private  IProgressHandler NotifyProgress;
        private System.Collections.Generic.SortedDictionary<long, List<Engine.Business.Abstract.CollectionItem>> SizeDictionary;
        private System.Collections.Generic.SortedDictionary<long, List<Engine.Business.Abstract.CollectionItem>> CRC32Dictionary;
        private Queue<Business.Model.Operation> OperationsQueue;
        //private Queue<Business.Model.Operation> OperationsQueue;
        private Thread OperationThread;
        private bool isOperationThreadRunning = false;


        #endregion

        #region CTOR
        public CsvHunter(CSV.CsvHuntInfo hi, 
            Business.Interfaces.IScanner sc,
            IProgressHandler ph
            )
        {
            HuntInfo = (CsvHuntInfo)hi;
            Scanner = sc;
            NotifyProgress = ph;
            IOTools = new IO.FileTools(this, hi.TypeofProcess, ph);
            SizeDictionary = new System.Collections.Generic.SortedDictionary<long, List<Engine.Business.Abstract.CollectionItem>>();
            CRC32Dictionary = new System.Collections.Generic.SortedDictionary<long, List<Engine.Business.Abstract.CollectionItem>>();
            OperationsQueue = new Queue<Business.Model.Operation>();
        }
        #endregion

        #region IHunter

        public int Total
        {
            get
            {
                if(HuntInfo.TypeofProcess == ProcessType.Verify)
                    return Scanner.Count;
                else
                    return IOTools.GetFilesCount(HuntInfo.FoldersToScan);
            }
        }

        public int Hunt(ref bool cont)
        {
            //start entity hunting 
            NotifyProgress.ProgressDesription = "Hunting for " + HuntInfo.Entity.Name;
            NotifyProgress.TotalProgress = this.Total;
            this.PrepareDictionary();

            if(DataSchema.Settings.SettingsMgr.UseBackgroundOperations)
                StartOperationThread();

            int count = IOTools.Search(HuntInfo.FoldersToScan, ref cont);

            if(DataSchema.Settings.SettingsMgr.UseBackgroundOperations)
                StopOperationThread();

            return count;
        }
        public int Verify(ref bool cont)
        {
            //start entity hunting 
            NotifyProgress.ProgressDesription = "Verifing for " + HuntInfo.Entity.Name;

            NotifyProgress.TotalProgress = this.Total;

            int i = 0;

            foreach (Engine.Business.Abstract.CollectionItem c in Scanner.Scan())
            {
                if (cont == false)
                    break;
                if (IOTools.VerifyFile(new System.IO.FileInfo(GetFullItemPath(c)), c))
                {
                    c.Notify(c, ItemState.Valid);
                    NotifyProgress.WriteToOutput("Verify - " + c.Name + " from " + c.Description + " is OK.", LogPriority.INFO);
                    ++i;
                }
                NotifyProgress.ProgressChanged();
            }
            return i;
        }
        #endregion

        #region IFileComparer

        public bool ProcessFile(System.IO.FileInfo fi, CollectionItem c)
        {
            if (isOperationThreadRunning == false)//exception ocured in OperationThread, do not produce more items
                return false;

            c.OriginalPath = fi.FullName;
            
            if (System.IO.Directory.Exists(HuntInfo.Entity.Path) == false)
            System.IO.Directory.CreateDirectory(HuntInfo.Entity.Path);

            if (System.IO.Directory.Exists(HuntInfo.Entity.Path + c.Description) == false)
                System.IO.Directory.CreateDirectory(GetItemFolder(c));
            
            
            if (new System.IO.FileInfo(GetFullItemPath(c)).Exists)
            {
                //fi.Delete();
                c.Notify(c, ItemState.Duplicated);
                NotifyProgress.WriteToOutput(c.Name + " from " + c.Description + " was duplicated.", LogPriority.INFO);
            }
            else
            {
                    if (DataSchema.Settings.SettingsMgr.UseBackgroundOperations)
                    {
                        Business.Model.Operation op = new Business.Model.Operation();
                        op.From = fi.FullName;
                        op.To = GetFullItemPath(c);
                        op.OperationType = Operations.Copy;
                        op.Item = c;
                        this.QueueOperation(op);
                    }
                    else
                    {
                        try
                        {
                            IO.FileTools.CopyItem(fi, GetFullItemPath(c), this.NotifyProgress);
                            c.Notify(c, ItemState.New);
                        }
                        catch (System.IO.IOException ioex)
                        {
                            c.Notify(c, ItemState.Error);
                            throw ioex;
                        }
                        catch (Exception ioex)
                        {
                            c.Notify(c, ItemState.Error);
                            NotifyProgress.WriteToOutput(c.Name + " from " + c.Description + " can't be processed because " + ioex.ToString(), LogPriority.ERROR);
                        }
                    }
                    return true;


            }
            return false;
        }

        private CollectionItem MatchByCRC32(System.IO.FileInfo fi)
        {
            NotifyProgress.WriteToOutput("Matching by CRC32 for file " + fi.Name, LogPriority.DEBUG);
            
            long crc = -1;
            try
            {
                crc = this.IOTools.GetFileCRC32(fi);
                if (crc != -1 && this.CRC32Dictionary.ContainsKey(crc))//this file is present in this collection
                {
                    List<CollectionItem> cil = this.CRC32Dictionary[crc];
                    foreach (CollectionItem ci in cil)
                    {
                        if(fi.Name == ci.Name)
                            return ci;
                    }
                    
                }
            }
            catch (Exception ex)
            {
                NotifyProgress.WriteToOutput("CRC32 calculation failed because " + ex.ToString() + " for file " + fi.Name, LogPriority.ERROR);
            }
            return null;
        }

        private CollectionItem MatchBySizeAndName(System.IO.FileInfo fi)
        {
            NotifyProgress.WriteToOutput("Matching by Size and Name for file " + fi.Name, LogPriority.DEBUG);

            List<CollectionItem> sizedcolitems = SizeDictionary[fi.Length];
            foreach (CollectionItem sizedcolitem in sizedcolitems)
            {
                if (sizedcolitem.Name == fi.Name)
                    return sizedcolitem;
                break;
            }
            return null;
        }

        public CollectionItem MatchFile(System.IO.FileInfo fi)
        {
            CollectionItem it = null;
            NotifyProgress.ProgressChanged();

            if(this.SizeDictionary.ContainsKey(fi.Length))//file with this size is registered
            {
                DateTime now = DateTime.Now;

                if (DataSchema.Settings.SettingsMgr.MatchFirstByCRC == true)
                {

                    it = MatchByCRC32(fi);
                    if (it == null)
                    {
                        it = MatchBySizeAndName(fi);
                        if (it != null)
                            NotifyProgress.WriteToOutput("File " + fi.Name + " from " + it.Description + " with size " + fi.Length.ToString() + " and CRC32 " + it.CRC32.ToString() + " not matched by its CRC32", LogPriority.WARNING);
                        it = null;
                    }
                }
                else
                {
                    it = MatchBySizeAndName(fi);
                    if(it == null)
                        it = MatchByCRC32(fi);
                }

                DateTime now1 = DateTime.Now;
                TimeSpan ts = now1 - now;
                NotifyProgress.WriteToOutput("Matching lasts " + ts.ToString() + " for file " + fi.Name, LogPriority.DEBUG);
                return it;
            }
            return null;
        }


        public CollectionItem Match1(System.IO.FileInfo fi)
        {
            NotifyProgress.ProgressChanged();

            foreach (Engine.Business.Abstract.CollectionItem c in Scanner.Scan())
            {
                if(this.IOTools.VerifyFile(fi, c))
                {
                    return c;
                }
            }
            return null;
        }
        #endregion

        #region PRIVATE

        #region OPERATION THREAD
        private void StartOperationThread()
        {
            isOperationThreadRunning = true;

            OperationsQueue = new Queue<Business.Model.Operation>();
            OperationThread = new Thread(ProcessOperations);

            OperationThread.Start();
        }

        public void StopOperationThread()
        {
            DateTime t = DateTime.Now;

            
            int count = OperationsQueue.Count;

            this.NotifyProgress.ProgressDesription = "Background operations in progress. Please Wait!";

            if (isOperationThreadRunning)
            {
                lock (OperationsQueue)
                {
                    isOperationThreadRunning = false;
                    Monitor.PulseAll(OperationsQueue);

                    Monitor.Wait(OperationsQueue);
                }
            }

            //OperationThread.Join();
            TimeSpan ts = DateTime.Now - t;
            this.NotifyProgress.WriteToOutput("StopOperationThread lasts " + ts.ToString() + ", " + count.ToString() + " was queued", LogPriority.DEBUG);
        }

        private void ProcessOperations()
        {
            DateTime t = DateTime.Now;
            Business.Model.Operation op = null;
            int count = 0;
            while (true)
            {
                lock (OperationsQueue)
                {

                    if (OperationsQueue.Count == 0)
                    {
                        if(isOperationThreadRunning == false)//there will be no more elements in the queue
                        {
                            Monitor.PulseAll(OperationsQueue);// let main thread go on
                            break;
                        }
                        Monitor.Wait(OperationsQueue);
                    }

                    else//(OperationsQueue.Count != 0)
                    {
                        op = OperationsQueue.Dequeue();
                    }
                }

                if(op != null)
                {
                    NotifyProgress.WriteToOutput("Queue - dequeue item " + op.From,  LogPriority.DEBUG);
                    try
                    {
                        ExecuteOperation(op);
                        ++count;
                    }
                    catch (System.IO.IOException ioex)
                    {
                        op.Item.Notify(op.Item, ItemState.Error);
                        this.NotifyProgress.WriteToOutput("Critical error while coping file from " + op.From + " to " + op.To + " because " + ioex.ToString(), LogPriority.ERROR);
                        isOperationThreadRunning = false;
                        lock (OperationsQueue)
                        {
                            Monitor.PulseAll(OperationsQueue);// let main thread go on
                        }
                        return;
                    }
                    catch(Exception ex)
                    {
                        this.NotifyProgress.WriteToOutput("Cant't copy file from " + op.From + " to " + op.To + " because " + ex.ToString() , LogPriority.ERROR);
                    }
                    
                }
                op = null;
            }
            TimeSpan ts = DateTime.Now - t;
            this.NotifyProgress.WriteToOutput("ProcessOperations Thread lasts " + ts.ToString() + ", " + count.ToString() + " was queued", LogPriority.DEBUG);
        }

        private void ExecuteOperation(Business.Model.Operation op)
        {
            NotifyProgress.WriteToOutput("ExecuteOperation begins for " + op.From, LogPriority.DEBUG);
            System.IO.FileInfo fi = new System.IO.FileInfo(op.From);
            System.IO.FileInfo fiTo = new System.IO.FileInfo(op.To);

            if (!fiTo.Exists)
            {
                switch (op.OperationType)
                {
                    case Operations.Copy:
                        IO.FileTools.CopyItem(fi, op.To, this.NotifyProgress);
                        op.Item.Notify(op.Item, ItemState.New);
                        break;
                    default:
                        break;
                }
            }
            else
                NotifyProgress.WriteToOutput(op.From + " already hunted in this session.", LogPriority.INFO);

            NotifyProgress.WriteToOutput("ExecuteOperation ends to " + op.From, LogPriority.DEBUG);
        }

        private void QueueOperation(Business.Model.Operation op)
        {
            if (op != null)
            {
                NotifyProgress.WriteToOutput("Queue - queue item " + op.From,  LogPriority.DEBUG);
                lock (this.OperationsQueue)
                {
                    OperationsQueue.Enqueue(op);
                    Monitor.PulseAll(OperationsQueue);
                }
            }
        }
        #endregion

        private void PrepareDictionary()
        {
            SizeDictionary.Clear();
            CRC32Dictionary.Clear();
            foreach (Engine.Business.Abstract.CollectionItem c in Scanner.Scan())
            {
                if (this.SizeDictionary.ContainsKey(c.Size) == false)
                {
                    List<CollectionItem> l = new List<CollectionItem>();
                    l.Add(c);
                    this.SizeDictionary.Add(c.Size, l);
                }
                else
                {
                    this.SizeDictionary[c.Size].Add(c);
                }

                if (this.CRC32Dictionary.ContainsKey(c.CRC32) == false)
                {
                    List<CollectionItem> l = new List<CollectionItem>();
                    l.Add(c);
                    this.CRC32Dictionary.Add(c.CRC32, l);
                }
                else
                {
                    this.CRC32Dictionary[c.CRC32].Add(c);
                }
            }
        }

        private string GetFullItemPath(Engine.Business.Abstract.CollectionItem c)
        {
            if(c.Description.EndsWith("\\") == false) 
                c.Description += "\\";
            if(c.LinkedPath == "")//it's not linked shot
                return HuntInfo.Entity.Path + c.Description + c.Name;
            return c.LinkedPath + c.Description + c.Name;
        }
        private string GetItemFolder(Engine.Business.Abstract.CollectionItem c)
        {
            return HuntInfo.Entity.Path + c.Description;
        }

        #endregion

        #region STATIC

        
        #endregion
    }
}
